/*
 * Copyright 2008 Wink Saville
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License version 3
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.saville.mc;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.*;
import java.lang.Thread;
import java.net.*;
import java.io.*;
import java.net.ServerSocket;

import com.saville.debug.*;
import com.saville.serdes.*;
import com.saville.msgcomp.*;

public class McMgr implements IMcMgr {

    public int df = Log.ERROR;

    /*
     * Must be overridden
     */
    protected void queueMsgToHandler(Msg msg, McId mcId) {
        throw new RuntimeException("McMgrBase: queueMsgToHandler not overridden");
    }
    
    /*
     * Get the one and only McMgr instance
     */
    public static IMcMgr getInstance() {
        return mInstance;
    }

    /*
     * Get the name of the instance, used for debugging
     */
    public String getName() {
        return mName;
    }

    /*
     * Register the mc
     * 
     * @return the McId
     */
    public McId register(String name, IMc mc) {
        return mWorker.register(name, mc);
    }

    /*
     * Register the mcId
     * 
     * @return the McId
     */
    public McId registerMcId(McId mcId) {
        return mWorker.registerMcId(mcId);
    }

    /*
     * Lookup the named Mc.
     * 
     * @return unique id for this Mc.
     */
    public int lookup(String name) {
        return mWorker.lookup(name);
    }

    /*
     * Lookup the named Mc.
     * 
     * @return McId for this Mc.
     */
    public McId lookupMcId(String name) {
        return mWorker.lookupMcId(name);
    }

    /*
     * Get a set of SessionIds
     * 
     * @return SessionIdSet
     */
    public IMcMgr.SessionIdSet getSessionIdSet() {
        return mWorker.getSessionIdSet();
    }

    /*
     * Get a session id
     */
    public long getSessionId() {
        return mWorker.getSessionId();
    }

    /*
     * Send the msg to the msg.dstId
     */
    public void sendMsg(Msg msg) {
        mWorker.sendMsg(msg);
    }

    /*
     * Sends a duplicate of msg reversing dstId/dstSubId and srcId/srcSubId and
     * sets dupMsg.status = status.
     */
    public void sendReplyMsg(Msg msg, int status) {
        sendMsg(getReplyMsg(msg, status));
    }

    /*
     * Get a new msg from the pool.
     */
    public Msg getMsg() {
        // TODO: Use a pool
        Msg m = new Msg();
        return m;
    }

    /*
     * Get a new msg setting some fields
     */
    public Msg getMsg(int srcId, int dstId, int guid, int cmd) {
        Msg m = getMsg();
        m.srcId = srcId;
        m.dstId = dstId;
        m.guid = guid;
        m.cmd = cmd;
        return m;
    }

    /*
     * return the message to the pool.
     */
    public void retMsg(Msg msg) {
        // TODO: Place back on pool
    }

    /*
     * duplicate the message
     * 
     * @return Msg (currently does not duplicate the msg.obj just references the
     * existing)
     */
    public Msg dupMsg(Msg msg) {
        Msg m = getMsg();
        m.dstId = msg.dstId;
        m.dstSubId = msg.dstSubId;
        m.srcId = msg.srcId;
        m.srcSubId = msg.srcSubId;
        m.guid = msg.guid;
        m.cmd = msg.cmd;
        m.tag = msg.tag;
        m.timeout = msg.timeout;
        m.status = msg.status;
        m.arg1 = msg.arg1;
        m.arg2 = msg.arg2;
        m.arg3 = msg.arg3;
        m.arg4 = msg.arg4;
        m.obj = msg.obj; // TODO: How to clone the object?
        return m;
    }

    /**
     * Duplidate the msg reversing dstId/dstSubId and srcId/srcSubId and setting
     * msg.status = status
     * 
     * @return msg
     */
    public Msg getReplyMsg(Msg msg, int status) {
        Msg m = getMsg();
        m.dstId = msg.srcId;
        m.dstSubId = msg.srcSubId;
        m.srcId = msg.dstId;
        m.srcSubId = msg.dstSubId;
        m.guid = msg.guid;
        m.cmd = McConst.cmdToReply(msg.cmd);
        m.tag = msg.tag;
        m.timeout = msg.timeout;
        m.status = status;
        m.arg1 = msg.arg1;
        m.arg2 = msg.arg2;
        m.arg3 = msg.arg3;
        m.arg4 = msg.arg4;
        m.obj = msg.obj; // TODO: How to clone the object?
        return m;
    }

    protected class McMgrMc extends ActiveMc {
        public McMgrMc(String name) {
            super(name);
            df = Log.ERROR;
        }

        @Override
        public void processMsg(Msg msg) {
            Msg replyMsg = null;
            Log.v(df, "%s: processMsg E", mName);

            if (msg.dstId == mMcId.id) {
                if (msg.is(McConst.MC_GUID, McConst.MC_CMD_STARTED)) {
                    Log.v(df, "%s: processingMsg; Got STARTED", mName);
                } else {
                    replyMsg = getReplyMsg(msg, McConst.STATUS_OK);
                    if (msg.is(McConst.MC_GUID, McConst.MCMGR_CMD_GET_SECONDARY_PORT)) {
                        Log.v(df, "%s: processingMsg; Got GET_SECONDARY_PORT", mName);
                        replyMsg.arg1 = mSecondaryPort.getAndIncrement();
                    } else if (msg.is(McConst.MC_GUID, McConst.MCMGR_CMD_REGISTER_MC)) {
                        /* mcId.handler is assumed to be set */
                        /* TODO: What if exception (i.e. msg.obj isn't an McId) */
                        McId mcId = (McId) msg.obj;
                        mcId = registerMcId(mcId);
                        
                        /* Now that the destination has an id update it ?? */
                        replyMsg.dstId = mcId.id;
                        replyMsg.arg1 = mcId.id;
                        Log.v(df, "%s: processingMsg; Got REGISTER_MC name='%s' id=%d", mName, mcId.name, mcId.id);
                    } else if (msg.is(McConst.MC_GUID, McConst.MCMGR_CMD_LOOKUP_MC)) {
                        String name = (String) msg.obj;
                        Log.v(df, "%s: processingMsg; Got LOOKUP_MC name='%s'", mName, name);
                        replyMsg.obj = lookupMcId(name);
                    } else {
                        Log.v(df, "%s: processingMsg; Got unknown cmd=0x%x", mName, msg.cmd);
                        replyMsg.status = McConst.STATUS_UNKNOWN_CMD;
                    }
                    sendMsg(replyMsg);
                }
            } else {
                Log.v(df, "%s: processMsg; issueMsg to dstId=%d", mName, msg.dstId);
                McId mcId = msgCompIdMapping.get(msg.dstId);
                if (mcId == null) {
                    Log.e(df, "%s: processMsg; error not expecting msgs to self", mName);
                    throw new RuntimeException(); // for the moment throw
                    // exception
                    // retVal = false;
                } else {
                    issueMsg(msg, mcId);
                }
            }

            Log.v(df, "%s: processMsg X", mName);
        }
    }

    protected class McServer implements Runnable {
        McServer(int port) {
            Log.v(df, "%s: McServer; creating on port=%d", mName, port);

            /** ******************************************************************************** */
            /* FIXME: Next statement required to see "... run() 2" below */
            mSecondaryPort.get();
            /* FIXME: Next statement required to see "... run() 3" below */
            mServerRunning = 0;
            /** ******************************************************************************** */

            mPort = port;
            mThread = new Thread(null, this, mName + "_McServer");
            mThread.start();

            Log.v(df, "%s: McServer; start called", mName);
        }

        public void run() {
            Log.v(df, "%s: McServer; run() E", mName);
            try {
                mServerSocket = new ServerSocket(mPort);
            } catch (IOException e) {
                Log.e(df, "%s: McServer; error creating server socket on port=%d", mName, mPort);
                mServerRunning = 2;
                return;
            }

            /** ******************************************************************************** */
            Log.v(df, "%s: McServer; run() 1", mName);
            /* FIXME: We'll die if mSecondary.get() isn't in McServer() */
            mSecondaryPort.set(mPort + 1);
            Log.v(df, "%s: McServer; run() 2", mName);
            /* FIXME: We'll die if mServerRunnging=0 isn't in McServer() */
            mServerRunning = 1;
            Log.v(df, "%s: McServer; run() 3", mName);
            /** ******************************************************************************** */

            if (false && (mPort == McConst.MCMGR_PRIMARY_PORT)) {
                /*
                 * TODO: McMaster
                 */
                Msg msg = getMsg();
                msg.guid = McConst.MC_GUID;
                msg.cmd = McConst.MCMASTER_CMD_REGISTER_DEV;
                DevId devId = new DevId();
                devId.urn = mName;
                devId.friendlyName = "fn-" + mName;
                msg.obj = devId;
                Msg replyMsg = issueSyncMsg("192.168.0.133", McConst.MCMASTER_PORT, msg);
                if (replyMsg == null) {
                    Log.e(df, "%s: Couldn't contact McMaster", mName);
                } else if (replyMsg.status != McConst.STATUS_OK) {
                    Log.e(df, "%s: Couldn't register device", mName);
                } else {
                    devId = (DevId) replyMsg.obj;
                    Log.v(df, "%s: RegisterDev success id=0x%x urn=%s fn=%s", mName, devId.id,
                        devId.urn, devId.friendlyName);
                }
            }

            while (mServerRunning == 1) {
                try {
                    Log.v(df, "%s: McServer; wait for client", mName);
                    Socket clientSocket = mServerSocket.accept();
                    Log.v(df, "%s: McServer; make responder for client", mName);
                    new McResponder(clientSocket);
                } catch (IOException e) {
                    Log.e(df, "%s: McServer; IOException e=%s", mName, e);
                } catch (Exception e) {
                    Log.e(df, "%s: McServer; Exception e=%s", mName, e);
                }
            }
            mServerRunning = 2;
            Log.e(df, "%s: McServer; Exiting very strange!!!", mName);
        }

        int mPort = 0;
        Thread mThread = null;
        ServerSocket mServerSocket = null;
    }

    protected class McResponder implements Runnable {
        public McResponder(Socket socket) {
            mMcResponderName = "McResponder" + socket.getLocalPort();
            Log.v(df, "%s: McResponder(); E", mMcResponderName);
            mSocket = socket;
            Thread thr = new Thread(null, this, mMcResponderName);
            thr.start();

            Log.v(df, "%s: McResponder(); X", mMcResponderName);
        }

        /*
         * Send a reply msg to the output stream os.
         */
        private void issueReplyMsg(DataOutputStream os, SerdesBinary serdes, Msg msg, int status)
                throws IOException {
            serdes.init();
            msg.cmd |= McConst.CMD_REPLY;
            msg.status = status;
            msg.serialize(serdes);
            byte d[] = serdes.data();
            os.writeInt(serdes.size());
            os.write(d, 0, serdes.size());
        }

        public void run() {
            byte data[] = new byte[16384];
            SerdesBinary serdes = new SerdesBinary();
            int MAX_LENGTH = 65536;

            try {
                Log.v(df, "%s: McResponder; run() E", mMcResponderName);

                mOutStream = new DataOutputStream(mSocket.getOutputStream());
                mInStream = new DataInputStream(mSocket.getInputStream());

                while (true) {
                    int len = mInStream.readInt();
                    if (len > MAX_LENGTH) {
                        throw new Exception("Length too large");
                    }
                    if (data.length < len) {
                        data = new byte[len];
                    }
                    mInStream.readFully(data, 0, len);
                    serdes.init(data, 0, len);
                    Msg msg = (Msg) serdes.readObject();
                    if (msg.is(McConst.MC_GUID, McConst.MCMGR_CMD_GET_SECONDARY_PORT)) {
                        msg.arg1 = mSecondaryPort.getAndIncrement();
                        issueReplyMsg(mOutStream, serdes, msg, McConst.STATUS_OK);
                        // retMsg(msg); // TODO: Not from a pool yet?
                    } else if (msg.is(McConst.MC_GUID, McConst.MCMGR_CMD_REGISTER_MC)) {
                        McId mcId = (McId) msg.obj;
                        mcId.handler = mMySelf;
                        msg.arg1 = registerMcId(mcId).id;
                        issueReplyMsg(mOutStream, serdes, msg, McConst.STATUS_OK);
                        // retMsg(msg);
                    } else if (msg.is(McConst.MC_GUID, McConst.MCMGR_CMD_LOOKUP_MC)) {
                        String name = (String) msg.obj;
                        msg.obj = lookupMcId(name);
                        issueReplyMsg(mOutStream, serdes, msg, McConst.STATUS_OK);
                        // retMsg(msg);
                    } else if (msg.is(McConst.MC_GUID, McConst.MCMGR_CMD_GET_SESSION_ID_SET)) {
                        msg.obj = getSessionIdSet();
                        issueReplyMsg(mOutStream, serdes, msg, McConst.STATUS_OK);
                        // retMsg(msg);
                    } else {
                        if (msg.remote) {
                            Log.v(df, "%s: McResponder; FIXME: remote msg", mMcResponderName);
                        } else {
                            sendMsg(msg);
                        }
                    }
                }
            } catch (EOFException e) {
                // Ignore EOF exceptions
            } catch (Exception e) {
                Log.d(df, "%s: McResponder; exception processing e=%s", mMcResponderName, e);
            } finally {
                try {
                    if (mOutStream != null)
                        mOutStream.close();
                    if (mInStream != null)
                        mInStream.close();
                } catch (Exception e) {
                    Log.e(df, "%s: McResponder; exception closing e=%s", mMcResponderName, e);
                }

                Log.v(df, "%s: McResponder; run() X", mMcResponderName);
            }
        }

        private Socket mSocket = null;
        private DataOutputStream mOutStream = null;
        private DataInputStream mInStream = null;
        private String mMcResponderName = null;
    }

    /*
     * Send the msg to the remote mcId
     */
    protected void issueMsg(Msg msg, McId mcId) {
        Socket socket = null;
        DataOutputStream outStream = null;

        try {
            if (mcId == null) {
                Log.e(df, "%s: issueMsg(); mcId is null", mName);
                throw new RuntimeException("McMgr: " + mName + " issueMsg(); mcId is null");
            }
            Log.v(df, "%s: issueMsg(); mcId name=%s ipAddr=%s ipPort=%d", mName, mcId.name,
                mcId.ipAddr, mcId.ipPort);
            
            socket = new Socket(mcId.ipAddr, mcId.ipPort);
            outStream = new DataOutputStream(socket.getOutputStream());

            // If the destination is to a remote Id then remap
            if (mcId.remote) {
                msg.dstId = mcId.remoteId;
                msg.remote = true;
            }

            /* Issue msg */
            SerdesBinary serdes = new SerdesBinary();
            msg.serialize(serdes);
            byte dataOut[] = serdes.data(); 
            outStream.writeInt(serdes.size());
            outStream.write(dataOut, 0, serdes.size());

            Log.v(df, "%s: issueMsg(); mcId name=%s ipAddr=%s ipPort=%d WROTE", mName, mcId.name,
                mcId.ipAddr, mcId.ipPort);
        } catch (IOException e) {
            Log.e(df, "%s: issueMsg; error e=%s", mName, e);
        } finally {
            try {
                if (outStream != null)
                    outStream.close();
            } catch (IOException e) {
                Log.e(df, "%s: issueMsg; error closing outStream e=%s", mName, e);
            }
            try {
                if (socket != null)
                    socket.close();
            } catch (IOException e) {
                Log.e(df, "%s: issueMsg; error closing socket e=%s", mName, e);
            }
        }
    }

    /*
     * Send a msg and wait for the reply to addr:port.
     * 
     * TODO: Maybe this should be McId instead of addr:port?
     */
    protected Msg issueSyncMsg(String addr, int port, Msg msg) {
        Msg m = null;
        Socket socket = null;
        DataOutputStream outStream = null;
        DataInputStream inStream = null;

        try {
            socket = new Socket(addr, port);
            outStream = new DataOutputStream(socket.getOutputStream());
            inStream = new DataInputStream(socket.getInputStream());

            /* Issue msg */
            SerdesBinary serdes = new SerdesBinary();
            msg.serialize(serdes);
            byte dataOut[] = serdes.data();
            outStream.writeInt(serdes.size());
            outStream.write(dataOut, 0, serdes.size());

            /* Get reply */
            int len = inStream.readInt();
            byte dataIn[] = new byte[len];
            inStream.readFully(dataIn, 0, len);
            serdes.init(dataIn, 0, len);
            m = (Msg) serdes.readObject();
        } catch (IOException e) {
            Log.e(df, "%s: issueSyncMsg; error e=%s", mName, e);
        } finally {
            try {
                if (outStream != null)
                    outStream.close();
            } catch (IOException e) {
                Log.e(df, "%s: issueSyncMsg; error closing outStream e=%s", mName, e);
            }
            try {
                if (inStream != null)
                    inStream.close();
            } catch (IOException e) {
                Log.e(df, "%s: issueSyncMsg; error closing inStream e=%s", mName, e);
            }
            try {
                if (socket != null)
                    socket.close();
            } catch (IOException e) {
                Log.e(df, "%s: issueSyncMsg; error closing socket e=%s", mName, e);
            }
        }

        return m;
    }

    /*
     * Send msg to the primary McMgr and wait for reply.
     * 
     * @return reply msg
     */
    protected Msg issueSyncMsgToPrimary(Msg msg) {
        return issueSyncMsg("127.0.0.1", McConst.MCMGR_PRIMARY_PORT, msg);
    }

    /*
     * Get the secondary port from the primary McMgr
     * 
     * @return unique id
     */
    protected int issueSyncMsgToPrimary_GetSecondaryPort() {
        int port;
        Msg replyMsg = null;

        Msg msg = getMsg();
        msg.guid = McConst.MC_GUID;
        msg.cmd = McConst.MCMGR_CMD_GET_SECONDARY_PORT;

        replyMsg = issueSyncMsgToPrimary(msg);

        if (replyMsg != null) {
            port = (int) replyMsg.arg1;
            retMsg(replyMsg);
        } else {
            port = -1;
        }

        retMsg(msg);

        Log.v(df, "%s: issueSyncMsgToPrimary_GetSecondaryPort port=%d", mName, port);
        return port;
    }

    /*
     * Register the mcId with the primary McMgr
     * 
     * @return unique id
     */
    protected int issueSyncMsgToPrimary_RegisterMc(McId mcId) {
        int id;
        Msg replyMsg = null;

        Msg msg = getMsg();
        msg.guid = McConst.MC_GUID;
        msg.cmd = McConst.MCMGR_CMD_REGISTER_MC;
        msg.obj = mcId;

        replyMsg = issueSyncMsgToPrimary(msg);
        if (replyMsg != null) {
            id = (int) replyMsg.arg1;
            retMsg(replyMsg);
        } else {
            id = -1;
        }
        retMsg(msg);

        Log.v(df, "%s: issueSyncMsgToPrimary_RegisterMc name=%s id=%d", mName, mcId.name, id);
        return id;
    }

    /*
     * Lookup the MsgComp by name
     * 
     * @return McId for the MsgComp or null if none
     */
    protected McId issueSyncMsgToPrimary_LookupMc(String name) {
        McId mcId;
        Msg replyMsg;

        Log.v(df, "%s: issueSyncMsgToPrimary_LookupMc name=%s", mName, name);
        Msg msg = getMsg();
        msg.guid = McConst.MC_GUID;
        msg.cmd = McConst.MCMGR_CMD_LOOKUP_MC;
        msg.obj = new StringSer(name); // TODO: use pool for StringSer?

        replyMsg = issueSyncMsgToPrimary(msg);
        if ((replyMsg != null) && (replyMsg.obj != null)) {
            mcId = (McId) replyMsg.obj;
            retMsg(replyMsg);
            Log.v(df, "%s: issueSyncMsgToPrimary_LookupMc dstId=%d", mName, mcId.id);
        } else {
            mcId = null;
            Log.v(df, "%s: issueSyncMsgToPrimary_LookupMc error no reply", mName);
        }

        retMsg(msg);

        return mcId;
    }

    /*
     * Remote Lookup the MsgComp by name
     * 
     * @return McId for the MsgComp or null if none
     */
    protected McId issueSyncMsgToRemote_LookupMc(String addr, int port, String name) {
        McId mcId;
        Msg replyMsg;

        Log.v(df, "%s: issueSyncMsgToRemote_LookupMc addr=%s port=%d name=%s", mName, addr, port,
            name);
        Msg msg = getMsg();
        msg.guid = McConst.MC_GUID;
        msg.cmd = McConst.MCMGR_CMD_LOOKUP_MC;
        msg.obj = new StringSer(name); // TODO: use pool for StringSer?

        replyMsg = issueSyncMsg(addr, port, msg);
        if ((replyMsg != null) && (replyMsg.obj != null)) {
            mcId = (McId) replyMsg.obj;
            retMsg(replyMsg);
            Log.v(df, "%s: issueSyncMsgToRemote_LookupMc dstId=%d", mName, mcId.id);
        } else {
            mcId = null;
            Log.v(df, "%s: issueSyncMsgToRemote_LookupMc error no reply", mName);
        }

        retMsg(msg);

        return mcId;
    }

    /*
     * Get a sessionIdSet from master
     * 
     * @return SessionIdSet
     */
    protected IMcMgr.SessionIdSet issueSyncMsgToPrimary_GetSessionIdSet() {
        IMcMgr.SessionIdSet sids;
        Msg replyMsg;

        Log.v(df, "%s: issueSyncMsgToPrimary_GetSessionIdSet", mName);
        Msg msg = getMsg();
        msg.guid = McConst.MC_GUID;
        msg.cmd = McConst.MCMGR_CMD_GET_SESSION_ID_SET;

        replyMsg = issueSyncMsgToPrimary(msg);
        if ((replyMsg != null) && (replyMsg.obj != null)) {
            sids = (IMcMgr.SessionIdSet) replyMsg.obj;
            retMsg(replyMsg);
            Log.v(df, "%s: issueSyncMsgToPrimary_GetSessionIdSet first=%ld num=%ld", mName, sids.firstSessionId,
                sids.numSessionIds);
        } else {
            sids = null;
            Log.v(df, "%s: issueSyncMsgToPrimary_GetSessionIdSet error no reply", mName);
        }

        retMsg(msg);

        return sids;
    }

    /*
     * Abstract class for the methods that have different implementations
     * between the Primary and Secondary McMgr's
     */
    protected abstract class McMgrWorker {
        abstract McId register(String name, IMc mc);

        abstract int lookup(String name);
        
        abstract IMcMgr.SessionIdSet getSessionIdSet();
        
        abstract long getSessionId();

        public void sendMsg(Msg msg) {
            Log.v(df, "%s: McMgrWorker: sendMsg id=%d", mName, msg.dstId);
            McId mcId = msgCompIdMapping.get(msg.dstId);
            if (mcId == null) {
                Log.v(df, "%s: McMgrWorker: sendMsg bad dstId=%d DROP (TODO reply)", mName, msg.dstId);
            } else {
                Log.v(df, "%s: McMgrWorker: sendMsg name=%s", mName, mcId.name);
                mcId.handler.queueMsg(msg);
            }
        }

        public McId registerMcId(McId mcId) {
            // TODO: Handle duplicate error and any other errors

            /*
             * -1 is never valid but we've probably overflowed, but check none
             * the less.
             */
            do {
                mcId.id = mNextId.getAndIncrement();
            } while (mcId.id == -1);

            updateDatabases(mcId);
            Log.v(df, "%s: McMgrWorker.register McId name=%s id=%d", mName, mcId.name, mcId.id);
            return mcId;
        }

        public McId lookupMcId(String name) {
            McId mcId;
            int id;

            try {
                mcId = msgCompNameMapping.get(name);
                id = mcId.id;
            } catch (Exception e) {
                mcId = null;
                id = -1;
            }

            Log.v(df, "%s: McMgrWorker.lookupMcId name=%s id=%d", mName, name, id);
            return mcId;
        }

        protected void updateDatabases(McId mcId) {
            msgCompNameMapping.put(mcId.name, mcId);
            msgCompIdMapping.put(mcId.id, mcId);
        }
    }

    /*
     * The Primary McMgr implementation of McMgrWorker
     */
    protected class McMgrWorkerPrimary extends McMgrWorker {
        public McMgrWorkerPrimary() {
        }

        public McId register(String name, IMc mc) {
            McId mcId = new McId();
            mcId.handler = mc;
            mcId.name = name;
            mcId.ipAddr = "127.0.0.1"; // TODO: We need our real address/dns?
            mcId.ipPort = mServerPort;
            registerMcId(mcId);
            Log.v(df, "%s: McMgrWorkerPrimary.register name=%s id=%d", mName, name, mcId.id);
            return mcId;
        }

        public int lookup(String uriStr) {
            int id;

            try {
                /*
                 * Lookup locally
                 */
                id = msgCompNameMapping.get(uriStr).id;
            } catch (Exception e) {
                URI uri = null;
                String scheme = null;
                String host = null;
                int port = -1;
                boolean uriError;

                /*
                 * Try a remote lookup
                 */
                try {
                    uri = new URI(uriStr);
                    scheme = uri.getScheme();
                    host = uri.getHost();
                    port = uri.getPort();
                    uriError = false;
                } catch (URISyntaxException uriE) {
                    Log.e(df, "%s: McMgrWorker.Primary.lookup(name) URISyntaxException e=%s",
                        mName, uriE);
                    uriError = true;
                }

                if (!uriError && (scheme != null) && (scheme.compareToIgnoreCase("amc") == 0)) {
                    /*
                     * TODO: What to do url's which are semantically the same
                     * but textually different. Two easy examples are the port
                     * could be absent or the scheme could be different cases.
                     */
                    if (port == -1) {
                        port = McConst.MCMGR_PRIMARY_PORT;
                    }

                    String path = uri.getPath();
                    if (path.charAt(0) == '/') {
                        path = path.substring(1);
                    }

                    McId mcId = issueSyncMsgToRemote_LookupMc(host, port, path);
                    if (mcId != null) {
                        mcId.remote = true;
                        mcId.remoteId = mcId.id;
                        mcId.name = uriStr;
                        mcId.handler = mMySelf;
                        registerMcId(mcId);
                        id = mcId.id;
                        Log.v(df, "%s: remote lookup mcId=%s", mName, mcId);
                    } else {
                        id = -1;
                    }
                } else {
                    id = -1;
                }
            }

            Log.v(df, "%s: McMgrWorkerPrimary.lookup uriStr=%s id=%d", mName, uriStr, id);
            return id;
        }
        
        public IMcMgr.SessionIdSet getSessionIdSet() {
            IMcMgr.SessionIdSet sids = new IMcMgr.SessionIdSet();
            sids.numSessionIds = 16;
            
            sids.firstSessionId = mSessionId.getAndAdd(sids.numSessionIds);
            if (mSessionId.get() < 0) {
                /* We've run out of session Ids */
                sids.firstSessionId = -1;
                sids.numSessionIds = 0;
            }
                
            return sids;
        }
        
        public long getSessionId() {
            if (mSessionIdSet.numSessionIds == 0) {
                mSessionIdSet = getSessionIdSet();
                if ((mSessionIdSet == null) || (mSessionIdSet.numSessionIds <= 0)) {
                    throw new RuntimeException("No more session Ids");
                }
                mSessionId.set(mSessionIdSet.firstSessionId);
            }
            return mSessionId.getAndIncrement();
        }
    }

    /*
     * The Secondary McMgr implementation of McMgrWorker
     */
    protected class McMgrWorkerSecondary extends McMgrWorker {
        public McMgrWorkerSecondary() {
        }

        public McId register(String name, IMc handler) {
            McId mcId = new McId();
            mcId.name = name;
            mcId.ipAddr = "127.0.0.1";
            mcId.ipPort = mServerPort;
            mcId.handler = handler;
            mcId.id = issueSyncMsgToPrimary_RegisterMc(mcId);
            if (mcId.id >= 0) {
                updateDatabases(mcId);
            }
            Log.v(df, "%s: McMgrWorkerSecondary.register name=%s id=%d", mName, name, mcId.id);
            return mcId;
        }

        public int lookup(String name) {
            int id;

            /*
             * Check locally if it fails ask server
             */
            try {
                id = msgCompNameMapping.get(name).id;
            } catch (Exception e) {
                Log.v(df, "%s: McMgrWorkerSecondary: lookup failed", mName);
                id = -1;
            }

            if (id == -1) {
                /* Ask server */
                McId mcId = issueSyncMsgToPrimary_LookupMc(name);
                if (mcId != null) {
                    mcId.handler = mMySelf;
                    updateDatabases(mcId);
                    id = mcId.id;
                } else {
                    Log.v(df, "%s: McMgrWorkerSecondary: Server lookup failed", mName);
                    id = -1;
                }
            }

            Log.v(df, "%s: McMgrWorkerSecondary.lookup name=%s id=%d", mName, name, id);
            return id;
        }
        
        public IMcMgr.SessionIdSet getSessionIdSet() {
            return issueSyncMsgToPrimary_GetSessionIdSet();
        }
        
        public long getSessionId() {
            if (mSessionIdSet.numSessionIds == 0) {
                mSessionIdSet = issueSyncMsgToPrimary_GetSessionIdSet();
                if ((mSessionIdSet == null) || (mSessionIdSet.numSessionIds <= 0)) {
                    throw new RuntimeException("No more session Ids");
                }
                mSessionId.set(mSessionIdSet.firstSessionId);
            }
            return mSessionId.getAndIncrement();
        }
    }

    protected void sleep(int millisecs) {
        try {
            Thread.sleep(millisecs);
        } catch (InterruptedException e) {
        }
    }
    
    /*
     * Name of the McMgr
     */
    protected String mName;

    /*
     * The next TCP port that the Primary McMgr will return.
     */
    protected AtomicInteger mSecondaryPort;

    /*
     * The next id to use for McId
     */
    protected AtomicInteger mNextId;
    
    /*
     * Session Id
     */
    protected AtomicLong mSessionId = new AtomicLong();

    /*
     * Session Id set
     */
    protected IMcMgr.SessionIdSet mSessionIdSet = new IMcMgr.SessionIdSet();

    /*
     * The actual TCP port for this McMgr
     */
    protected int mServerPort;

    /*
     * 0 = if McServer is not running 1 = if McServer is running 2 = if McServer
     * couldn't start or stopped.
     * 
     * TODO: Make AtomicInteger?
     */
    protected volatile int mServerRunning;

    /*
     * The McMgrWorkerPrimary or McMgrWorkerSecondary
     */
    protected McMgrWorker mWorker;

    /*
     * The McMgr's MsgComp
     */
    protected Mc mMySelf;

    /*
     * Mapping of Name to McId
     */
    protected Map<String, McId> msgCompNameMapping;

    /*
     * Map of unique id to McId
     */
    protected Map<Integer, McId> msgCompIdMapping;

    /*
     * The instance of the McMgr
     */
    protected static IMcMgr mInstance = new McMgr();
    
    /*
     * Singleton McMgr
     */
    McMgr() {
        mInstance = this;
        mName = "McMgr-" + Timing.readTsc();
        mSecondaryPort = new AtomicInteger();
        mNextId = new AtomicInteger();
        msgCompNameMapping = Collections.synchronizedMap(new HashMap<String, McId>());
        msgCompIdMapping = Collections.synchronizedMap(new HashMap<Integer, McId>());
        /*
         * Be sure we instantiate Serdes so we can register ISerdesable
         * entities. We could add this to the MIXIN ISerdes code but this should
         * be OK.
         */
        new SerdesBinary();

        /*
         * TODO: Define a way to do Serdes and ISerdesables automatically, maybe
         * manifests, Annotation or class loading.
         */
        new StringSer();
        new Msg();
        new McId();
        new DevId();

        mServerRunning = 0;
        mServerPort = McConst.MCMGR_PRIMARY_PORT;
        new McServer(mServerPort);
        while (mServerRunning == 0) {
            sleep(100);
        }

        if (mServerRunning == 1) {
            mWorker = new McMgrWorkerPrimary();
        } else {
            int i;
            final int retries = 5;

            for (i = 0; i < retries; i++) {
                mServerPort = issueSyncMsgToPrimary_GetSecondaryPort();
                if (mServerPort == -1)
                    throw new RuntimeException(
                            "McMgr: run(); Couldn't communicate with server to get secondary port");

                mServerRunning = 0;
                new McServer(mServerPort);
                while (mServerRunning == 0) {
                    sleep(100);
                }

                if (mServerRunning == 1) {
                    mWorker = new McMgrWorkerSecondary();
                    break;
                }
            }
            if (i == retries) {
                throw new RuntimeException(
                        "McMgr: InitHsm; Couldn't start McMgr as primary or secondary");
            }
        }

        mMySelf = new McMgrMc(mName);

        Log.v(df, "%s: X", mName);
    }
}
